#include "bitbuf.h"
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>

#define chk_ptr if (!ptr) return BUF_INCORRECT;
#define chk_realloc { enum BufError err; err = try_to_realloc(ptr, pos); if (err != BUF_OK) return err; }

char* buf_error_to_str(const enum BufError err) {
    switch (err) {
        case BUF_OK: return "BUF_OK";
        case BUF_ENDOFBUF_ERROR: return "BUF_ENDOFBUF_ERROR";
        case BUF_MALLOC_ERROR: return "BUF_MALLOC_ERROR";
        case BUF_INCORRECT: return "BUF_INCORRECT";
        default: { static char str[32]; sprintf(str, "Unknown(%d)\0", err); return str; }
    }
}

enum BufError try_to_realloc(struct BitBuf *ptr, const uint32_t min_size) {
    chk_ptr
    uint32_t new_size = ptr->size + min_size + 1024;
    char* new_buf = realloc(ptr->buf, new_size);
    if (new_buf == NULL) return BUF_MALLOC_ERROR;
    ptr->buf = new_buf;
    ptr->size = new_size;
    return BUF_OK;
}

enum BufError put_skip(struct BitBuf *ptr, const uint32_t count) {
    chk_ptr
    uint32_t pos = ptr->offset + count;
    if (pos >= ptr->size) chk_realloc
    for (uint32_t i = 0; i < count; i++) ptr->buf[ptr->offset + i] = 0;
    ptr->offset = pos;
    return BUF_OK;
}

enum BufError put_to_offset(struct BitBuf *ptr, const uint32_t offset, const char* data, const uint32_t size) {
    chk_ptr
    uint32_t pos = offset + size;
    if (pos >= ptr->size) chk_realloc
    for (uint32_t i = 0; i < size; i++) ptr->buf[offset + i] = data[i];
    return BUF_OK;
}
enum BufError put(struct BitBuf *ptr, const char* data, const uint32_t size) {
    chk_ptr
    enum BufError err = put_to_offset(ptr, ptr->offset, data, size); chk_err
    ptr->offset += size;
    return BUF_OK;
}

enum BufError put_u8_to_offset(struct BitBuf *ptr, const uint32_t offset, const uint8_t val) {
    chk_ptr
    uint32_t pos = offset + sizeof(uint8_t);
    if (pos >= ptr->size) chk_realloc
    ptr->buf[offset + 0] = val & 0xff;
    return BUF_OK;
}
enum BufError put_u8(struct BitBuf *ptr, uint8_t val) {
    chk_ptr
    enum BufError err = put_u8_to_offset(ptr, ptr->offset, val); chk_err
    ptr->offset += sizeof(uint8_t);
    return BUF_OK;
}

enum BufError put_u16_be_to_offset(struct BitBuf *ptr, const uint32_t offset, const uint16_t val) {
    chk_ptr
    uint32_t pos = offset + sizeof(uint16_t);
    if (pos >= ptr->size) chk_realloc
    ptr->buf[offset + 0] = (val >> 8) & 0xff;
    ptr->buf[offset + 1] = (val >> 0) & 0xff;
    return BUF_OK;
}
enum BufError put_u16_be(struct BitBuf *ptr, const uint16_t val) {
    chk_ptr
    enum BufError err = put_u16_be_to_offset(ptr, ptr->offset, val); chk_err
    ptr->offset += sizeof(uint16_t);
    return BUF_OK;
}

enum BufError put_u16_le_to_offset(struct BitBuf *ptr, const uint32_t offset, const uint16_t val) {
    chk_ptr
    uint32_t pos = offset + sizeof(uint16_t);
    if (pos >= ptr->size) chk_realloc
    ptr->buf[offset + 0] = (val >> 0) & 0xff;
    ptr->buf[offset + 1] = (val >> 8) & 0xff;
    return BUF_OK;
}
enum BufError put_u16_le(struct BitBuf *ptr, const uint16_t val) {
    chk_ptr
    enum BufError err = put_u16_le_to_offset(ptr, ptr->offset, val); chk_err
    ptr->offset += sizeof(uint16_t);
    return BUF_OK;
}

enum BufError put_u32_be_to_offset(struct BitBuf *ptr, const uint32_t offset, const uint32_t val) {
    chk_ptr
    uint32_t pos = offset + sizeof(uint32_t);
    if (pos >= ptr->size) chk_realloc
    ptr->buf[offset + 0] = (val >> 24) & 0xff;
    ptr->buf[offset + 1] = (val >> 16) & 0xff;
    ptr->buf[offset + 2] = (val >>  8) & 0xff;
    ptr->buf[offset + 3] = (val >>  0) & 0xff;
    return BUF_OK;
}
enum BufError put_u32_be(struct BitBuf *ptr, const uint32_t val) {
    chk_ptr
    enum BufError err = put_u32_be_to_offset(ptr, ptr->offset, val); chk_err
    ptr->offset += sizeof(uint32_t);
    return BUF_OK;
}
enum BufError put_i32_be(struct BitBuf *ptr, const int32_t val) {
    chk_ptr
    enum BufError err = put_u32_be_to_offset(ptr, ptr->offset, val); chk_err
    ptr->offset += sizeof(int32_t);
    return BUF_OK;
}

enum BufError put_u64_be_to_offset(struct BitBuf *ptr, const uint32_t offset, const uint64_t val) {
    chk_ptr
    uint32_t pos = offset + sizeof(uint64_t);
    if (pos > ptr->size) chk_realloc
    ptr->buf[offset + 0] = (val >> 56) & 0xff;
    ptr->buf[offset + 1] = (val >> 48) & 0xff;
    ptr->buf[offset + 2] = (val >> 40) & 0xff;
    ptr->buf[offset + 3] = (val >> 32) & 0xff;
    ptr->buf[offset + 4] = (val >> 24) & 0xff;
    ptr->buf[offset + 5] = (val >> 16) & 0xff;
    ptr->buf[offset + 6] = (val >>  8) & 0xff;
    ptr->buf[offset + 7] = (val >>  0) & 0xff;
    return BUF_OK;
}
enum BufError put_u64_be(struct BitBuf *ptr, const uint64_t val) {
    chk_ptr
    enum BufError err = put_u64_be_to_offset(ptr, ptr->offset, val); chk_err
    ptr->offset += sizeof(uint64_t);
    return BUF_OK;
}

enum BufError put_u32_le_to_offset(struct BitBuf *ptr, const uint32_t offset, const uint32_t val) {
    chk_ptr
    uint32_t pos = offset + 4;
    if (pos >= ptr->size) chk_realloc
    ptr->buf[offset + 0] = (val >>  0) & 0xff;
    ptr->buf[offset + 1] = (val >>  8) & 0xff;
    ptr->buf[offset + 2] = (val >> 16) & 0xff;
    ptr->buf[offset + 3] = (val >> 24) & 0xff;
    return BUF_OK;
}
enum BufError put_u32_le(struct BitBuf *ptr, const uint32_t val) {
    chk_ptr
    enum BufError err = put_u32_le_to_offset(ptr, ptr->offset, val); chk_err
    ptr->offset += sizeof(uint32_t);
    return BUF_OK;
}

enum BufError put_str4_to_offset(struct BitBuf *ptr, const uint32_t offset, const char str[4]) {
    chk_ptr
    uint32_t pos = offset + 4;
    if (pos >= ptr->size) chk_realloc
    for (uint8_t i = 0; i < 4; i++) { ptr->buf[offset + i] = str[i]; }
    return BUF_OK;
}
enum BufError put_str4(struct BitBuf *ptr, const char str[4]) {
    chk_ptr
    enum BufError err = put_str4_to_offset(ptr, ptr->offset, str); chk_err
    ptr->offset += 4;
    return BUF_OK;
}
enum BufError put_counted_str_to_offset(struct BitBuf *ptr, const uint32_t offset, const char *str, const uint32_t len) {
    chk_ptr
    uint32_t pos = offset + len + 1;
    if (pos >= ptr->size) chk_realloc
    for (uint32_t i = 0; i < len+1; i++) { ptr->buf[offset + i] = str[i]; }
    ptr->buf[pos] = 0;
    return BUF_OK;
    }
enum BufError put_counted_str(struct BitBuf *ptr, const char *str, const uint32_t len) {
    chk_ptr
    enum BufError err = put_counted_str_to_offset(ptr, ptr->offset, str, len); chk_err
    ptr->offset += len+1;
    return BUF_OK;
}

// enum BufError hexStr(struct BitBuf *ptr, char *str) : string {
//     let str = '';
//     for(let i = 0; i < this.offset; i++) {
//         if (i % 40 === 0) str += '\n';
//         if (i % 4 === 0 && i+1 < this.data.length) {
//             if (i > 0 && i % 40 !== 0) str += ' ';
//             str += '0x';
//         }
//         str += decimalToHex(this.data[i]);
//     }
//     return str;
// }
